/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.auth;

import com.je.auth.check.context.model.AuthRequest;
import com.je.auth.exception.OAuth2Exception;
import com.je.auth.model.*;
import com.je.auth.check.util.FoxUtil;

import java.util.List;

/**
 * OAuth2 模块 代码实现
 *
 * @author kong
 */
public interface OAuth2Template {

    OAuth2Engine getoAuth2Engine();

    void setoAuth2Engine(OAuth2Engine oAuth2Engine);

    // ------------------- 获取数据 (开发者必须重写的函数)

    /**
     * 根据id获取Client信息
     *
     * @param clientId 应用id
     * @return ClientModel
     */
    ClientModel getClientModel(String clientId);

    /**
     * 根据ClientId 和 LoginId 获取openid
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return 此账号在此Client下的openid
     */
    String getOpenid(String clientId, Object loginId);

    // ------------------- 资源校验API

    /**
     * 根据id获取Client信息, 如果Client为空，则抛出异常
     *
     * @param clientId 应用id
     * @return ClientModel
     */
    default ClientModel checkClientModel(String clientId) throws OAuth2Exception {
        ClientModel clientModel = getClientModel(clientId);
        if (clientModel == null) {
            throw new OAuth2Exception("无效client_id: " + clientId);
        }
        return clientModel;
    }

    /**
     * 获取 Access-Token，如果AccessToken为空则抛出异常
     *
     * @param accessToken .
     * @return .
     */
    default AccessTokenModel checkAccessToken(String accessToken) throws OAuth2Exception {
        AccessTokenModel at = getAccessToken(accessToken);
        if (at == null) {
            throw new OAuth2Exception("无效access_token：" + accessToken);
        }
        return at;
    }

    /**
     * 获取 Client-Token，如果ClientToken为空则抛出异常
     *
     * @param clientToken .
     * @return .
     */
    default ClientTokenModel checkClientToken(String clientToken) throws OAuth2Exception {
        ClientTokenModel ct = getClientToken(clientToken);
        if (ct == null) {
            throw new OAuth2Exception("无效：client_token");
        }
        return ct;
    }

    /**
     * 获取 Access-Token 所代表的LoginId
     *
     * @param accessToken Access-Token
     * @return LoginId
     */
    default Object getLoginIdByAccessToken(String accessToken) throws OAuth2Exception {
        return checkAccessToken(accessToken).loginId;
    }

    /**
     * 校验：指定 Access-Token 是否具有指定 Scope
     *
     * @param accessToken Access-Token
     * @param scopes      需要校验的权限列表
     */
    default void checkScope(String accessToken, String... scopes) throws OAuth2Exception {
        if (scopes == null || scopes.length == 0) {
            return;
        }
        AccessTokenModel at = checkAccessToken(accessToken);
        List<String> scopeList = FoxUtil.convertStringToList(at.scope);
        for (String scope : scopes) {
            if (!scopeList.contains(scope)) {
                throw new OAuth2Exception("该 Access-Token 不具备 Scope：" + scope);
            }
        }
    }

    // ------------------- generate 构建数据

    /**
     * 构建Model：请求Model
     *
     * @param req     SaRequest对象
     * @param loginId 账号id
     * @return RequestAuthModel对象
     */
    default RequestAuthModel generateRequestAuth(AuthRequest req, Object loginId) {
        RequestAuthModel ra = new RequestAuthModel();
        ra.clientId = req.getParamNotNull(OAuth2Consts.Param.client_id);
        ra.responseType = req.getParamNotNull(OAuth2Consts.Param.response_type);
        ra.redirectUri = req.getParamNotNull(OAuth2Consts.Param.redirect_uri);
        ra.state = req.getParam(OAuth2Consts.Param.state);
        ra.scope = req.getParam(OAuth2Consts.Param.scope, "");
        ra.loginId = loginId;
        return ra;
    }

    /**
     * 构建Model：Code授权码
     *
     * @param ra 请求参数Model
     * @return 授权码Model
     */
    default CodeModel generateCode(RequestAuthModel ra) {

        // 删除旧Code
        deleteCode(getCodeValue(ra.clientId, ra.loginId));

        // 生成新Code
        String code = randomCode(ra.clientId, ra.loginId, ra.scope);
        CodeModel cm = new CodeModel(code, ra.clientId, ra.scope, ra.loginId, ra.redirectUri);

        // 保存新Code
        saveCode(cm);
        saveCodeIndex(cm);

        // 返回
        return cm;
    }

    /**
     * 构建Model：Access-Token
     *
     * @param code 授权码Model
     * @return AccessToken Model
     */
    default AccessTokenModel generateAccessToken(String code) throws OAuth2Exception {

        // 1、先校验
        CodeModel cm = getCode(code);
        if (cm == null) {
            throw new OAuth2Exception("无效code");
        }

        // 2、删除旧Token
        deleteAccessToken(getAccessTokenValue(cm.clientId, cm.loginId));
        deleteRefreshToken(getRefreshTokenValue(cm.clientId, cm.loginId));

        // 3、生成token
        AccessTokenModel at = convertCodeToAccessToken(cm);
        RefreshTokenModel rt = convertAccessTokenToRefreshToken(at);
        at.refreshToken = rt.refreshToken;
        at.refreshExpiresTime = rt.expiresTime;

        // 4、保存token
        saveAccessToken(at);
        saveAccessTokenIndex(at);
        saveRefreshToken(rt);
        saveRefreshTokenIndex(rt);

        // 5、删除此Code
        deleteCode(code);
        deleteCodeIndex(cm.clientId, cm.loginId);

        // 6、返回 Access-Token
        return at;
    }

    /**
     * 刷新Model：根据 Refresh-Token 生成一个新的 Access-Token
     *
     * @param refreshToken Refresh-Token值
     * @return 新的 Access-Token
     */
    default AccessTokenModel refreshAccessToken(String refreshToken) throws OAuth2Exception {

        // 获取 Refresh-Token 信息
        RefreshTokenModel rt = getRefreshToken(refreshToken);
        if (rt == null) {
            throw new OAuth2Exception("无效refresh_token: " + refreshToken);
        }

        // 如果配置了[每次刷新产生新的Refresh-Token]
        if (checkClientModel(rt.clientId).getIsNewRefresh()) {
            // 删除旧 Refresh-Token
            deleteRefreshToken(rt.refreshToken);

            // 创建并保持新的 Refresh-Token
            rt = convertRefreshTokenToRefreshToken(rt);
            saveRefreshToken(rt);
            saveRefreshTokenIndex(rt);
        }

        // 删除旧 Access-Token
        deleteAccessToken(getAccessTokenValue(rt.clientId, rt.loginId));

        // 生成新 Access-Token
        AccessTokenModel at = convertRefreshTokenToAccessToken(rt);

        // 保存新 Access-Token
        saveAccessToken(at);
        saveAccessTokenIndex(at);

        // 返回新 Access-Token
        return at;
    }

    /**
     * 构建Model：Access-Token (根据RequestAuthModel构建，用于隐藏式 and 密码式)
     *
     * @param ra         请求参数Model
     * @param isCreateRt 是否生成对应的Refresh-Token
     * @return Access-Token Model
     */
    default AccessTokenModel generateAccessToken(RequestAuthModel ra, boolean isCreateRt) throws OAuth2Exception {

        // 1、删除 旧Token
        deleteAccessToken(getAccessTokenValue(ra.clientId, ra.loginId));
        if (isCreateRt) {
            deleteRefreshToken(getRefreshTokenValue(ra.clientId, ra.loginId));
        }

        // 2、生成 新Access-Token
        String newAtValue = randomAccessToken(ra.clientId, ra.loginId, ra.scope);
        AccessTokenModel at = new AccessTokenModel(newAtValue, ra.clientId, ra.loginId, ra.scope);
        at.openid = getOpenid(ra.clientId, ra.loginId);
        at.expiresTime = System.currentTimeMillis() + (checkClientModel(ra.clientId).getAccessTokenTimeout() * 1000);

        // 3、生成&保存 Refresh-Token
        if (isCreateRt) {
            RefreshTokenModel rt = convertAccessTokenToRefreshToken(at);
            saveRefreshToken(rt);
            saveRefreshTokenIndex(rt);
        }

        // 5、保存 新Access-Token
        saveAccessToken(at);
        saveAccessTokenIndex(at);

        // 6、返回 新Access-Token
        return at;
    }

    /**
     * 构建Model：Client-Token
     *
     * @param clientId 应用id
     * @param scope    授权范围
     * @return Client-Token Model
     */
    default ClientTokenModel generateClientToken(String clientId, String scope) throws OAuth2Exception {
        // 1、删掉旧 Past-Token
        deleteClientToken(getPastTokenValue(clientId));

        // 2、将旧Client-Token 标记为新 Past-Token
        ClientTokenModel oldCt = getClientToken(getClientTokenValue(clientId));
        savePastTokenIndex(oldCt);

        // 2.5、如果配置了 PastClientToken 的 ttl ，则需要更新一下
        ClientModel cm = checkClientModel(clientId);
        if (oldCt != null && cm.getPastClientTokenTimeout() != -1) {
            oldCt.expiresTime = System.currentTimeMillis() + (cm.getPastClientTokenTimeout() * 1000);
            saveClientToken(oldCt);
        }

        // 3、生成新Client-Token
        ClientTokenModel ct = new ClientTokenModel(randomClientToken(clientId, scope), clientId, scope);
        ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000);

        // 3、保存新Client-Token
        saveClientToken(ct);
        saveClientTokenIndex(ct);

        // 4、返回
        return ct;
    }

    /**
     * 构建登录重定向接口
     *
     * @param clientId     客户端ID
     * @param redirectUri  重定向地址
     * @param scope        申请授权范围
     * @param responseType 响应类型
     * @param state        状态标识
     * @return
     */
    default String buildLoginRedirectUri(String clientId, String redirectUri, String scope, String responseType, String state) throws OAuth2Exception {
        String url = getoAuth2Engine().getoAuth2Config().getLoginView();
        if (FoxUtil.isEmpty(url)) {
            throw new OAuth2Exception("The confirm view is empty!");
        }
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.client_id, clientId);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.redirect_uri, redirectUri);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.response_type, responseType);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.scope, scope);
        if (FoxUtil.isEmpty(state) == false) {
            url = FoxUtil.joinParam(url, OAuth2Consts.Param.state, state);
        }
        return url;
    }

    /**
     * 构建确认重定向页面
     *
     * @param clientId     客户端ID
     * @param redirectUri  重定向ID
     * @param scope        申请授权范围
     * @param responseType 响应类型
     * @param state        状态标识
     * @return
     */
    default String buildConfirmRedirectUri(String clientId, String redirectUri, String scope, String responseType, String state) throws OAuth2Exception {
        String url = getoAuth2Engine().getoAuth2Config().getConfirmView();
        if (FoxUtil.isEmpty(url)) {
            throw new OAuth2Exception("The confirm view is empty!");
        }
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.client_id, clientId);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.redirect_uri, redirectUri);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.response_type, responseType);
        url = FoxUtil.joinParam(url, OAuth2Consts.Param.scope, scope);
        if (FoxUtil.isEmpty(state) == false) {
            url = FoxUtil.joinParam(url, OAuth2Consts.Param.state, state);
        }
        return url;
    }

    /**
     * 构建URL：下放Code URL (Authorization Code 授权码)
     *
     * @param redirectUri 下放地址
     * @param code        code参数
     * @param state       state参数
     * @return 构建完毕的URL
     */
    default String buildRedirectUri(String redirectUri, String code, String state) {
        String url = FoxUtil.joinParam(redirectUri, OAuth2Consts.Param.code, code);
        if (FoxUtil.isEmpty(state) == false) {
            url = FoxUtil.joinParam(url, OAuth2Consts.Param.state, state);
        }
        return url;
    }

    /**
     * 构建URL：下放Access-Token URL （implicit 隐藏式）
     *
     * @param redirectUri 下放地址
     * @param token       token
     * @param state       state参数
     * @return 构建完毕的URL
     */
    default String buildImplicitRedirectUri(String redirectUri, String token, String state) {
        String url = FoxUtil.joinSharpParam(redirectUri, OAuth2Consts.Param.token, token);
        if (FoxUtil.isEmpty(state) == false) {
            url = FoxUtil.joinSharpParam(url, OAuth2Consts.Param.state, state);
        }
        return url;
    }

    /**
     * 回收 Access-Token
     *
     * @param accessToken Access-Token值
     */
    default void revokeAccessToken(String accessToken) {

        // 如果查不到任何东西, 直接返回
        AccessTokenModel at = getAccessToken(accessToken);
        if (at == null) {
            return;
        }

        // 删除 Access-Token
        deleteAccessToken(accessToken);
        deleteAccessTokenIndex(at.clientId, at.loginId);

        // 删除对应的 Refresh-Token
        String refreshToken = getRefreshTokenValue(at.clientId, at.loginId);
        deleteRefreshToken(refreshToken);
        deleteRefreshTokenIndex(at.clientId, at.loginId);
    }

    // ------------------- check 数据校验

    /**
     * 判断：指定 loginId 是否对一个 Client 授权给了指定 Scope
     *
     * @param loginId  账号id
     * @param clientId 应用id
     * @param scope    权限
     * @return 是否已经授权
     */
    default boolean isGrant(Object loginId, String clientId, String scope) {
        List<String> grantScopeList = FoxUtil.convertStringToList(getGrantScope(clientId, loginId));
        List<String> scopeList = FoxUtil.convertStringToList(scope);
        return scopeList.size() == 0 || grantScopeList.containsAll(scopeList);
    }

    /**
     * 校验：该Client是否签约了指定的Scope
     *
     * @param clientId 应用id
     * @param scope    权限(多个用逗号隔开)
     */
    default void checkContract(String clientId, String scope) throws OAuth2Exception {
        List<String> clientScopeList = FoxUtil.convertStringToList(checkClientModel(clientId).contractScope);
        List<String> scopelist = FoxUtil.convertStringToList(scope);
        if (clientScopeList.containsAll(scopelist) == false) {
            throw new OAuth2Exception("请求的Scope暂未签约");
        }
    }

    /**
     * 校验：该Client使用指定url作为回调地址，是否合法
     *
     * @param clientId 应用id
     * @param url      指定url
     */
    default void checkRightUrl(String clientId, String url) throws OAuth2Exception {
        // 1、是否是一个有效的url
        if (FoxUtil.isUrl(url) == false) {
            throw new OAuth2Exception("无效redirect_url：" + url);
        }

        // 2、截取掉?后面的部分
        int qIndex = url.indexOf("?");
        if (qIndex != -1) {
            url = url.substring(0, qIndex);
        }

        // 3、是否在[允许地址列表]之中
        List<String> allowList = FoxUtil.convertStringToList(checkClientModel(clientId).allowUrl);
        if (getoAuth2Engine().getAuthEngine().getAuthCheckEngine().getAuthCheckStrategy().hasElement.apply(allowList, url) == false) {
            throw new OAuth2Exception("非法redirect_url：" + url);
        }
    }

    /**
     * 校验：clientId 与 clientSecret 是否正确
     *
     * @param clientId     应用id
     * @param clientSecret 秘钥
     * @return SaClientModel对象
     */
    default ClientModel checkClientSecret(String clientId, String clientSecret) throws OAuth2Exception {
        ClientModel cm = checkClientModel(clientId);
        if (cm.clientSecret == null || cm.clientSecret.equals(clientSecret) == false) {
            throw new OAuth2Exception("无效client_secret: " + clientSecret);
        }
        return cm;
    }

    /**
     * 校验：使用 code 获取 token 时提供的参数校验
     *
     * @param code         授权码
     * @param clientId     应用id
     * @param clientSecret 秘钥
     * @param redirectUri  重定向地址
     * @return CodeModel对象
     */
    default CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) throws OAuth2Exception {

        // 校验：Code是否存在
        CodeModel cm = getCode(code);
        if (cm == null) {
            throw new OAuth2Exception("无效code: " + code);
        }

        // 校验：ClientId是否一致
        if (cm.clientId.equals(clientId) == false) {
            throw new OAuth2Exception("无效client_id: " + clientId);
        }

        // 校验：Secret是否正确
        String dbSecret = checkClientModel(clientId).clientSecret;
        if (dbSecret == null || dbSecret.equals(clientSecret) == false) {
            throw new OAuth2Exception("无效client_secret: " + clientSecret);
        }

        // 如果提供了redirectUri，则校验其是否与请求Code时提供的一致
        if (FoxUtil.isEmpty(redirectUri) == false && redirectUri.equals(cm.redirectUri) == false) {
            throw new OAuth2Exception("无效redirect_uri: " + redirectUri);
        }

        // 返回CodeMdoel
        return cm;
    }

    /**
     * 校验：使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
     *
     * @param clientId     应用id
     * @param clientSecret 秘钥
     * @param refreshToken Refresh-Token
     * @return CodeModel对象
     */
    default RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) throws OAuth2Exception {

        // 校验：Refresh-Token是否存在
        RefreshTokenModel rt = getRefreshToken(refreshToken);
        if (rt == null) {
            throw new OAuth2Exception("无效refresh_token: " + refreshToken);
        }

        // 校验：ClientId是否一致
        if (rt.clientId.equals(clientId) == false) {
            throw new OAuth2Exception("无效client_id: " + clientId);
        }

        // 校验：Secret是否正确
        String dbSecret = checkClientModel(clientId).clientSecret;
        if (dbSecret == null || dbSecret.equals(clientSecret) == false) {
            throw new OAuth2Exception("无效client_secret: " + clientSecret);
        }

        // 返回Refresh-Token
        return rt;
    }

    /**
     * 校验：Access-Token、clientId、clientSecret 三者是否匹配成功
     *
     * @param clientId     应用id
     * @param clientSecret 秘钥
     * @param accessToken  Access-Token
     * @return SaClientModel对象
     */
    default AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) throws OAuth2Exception {
        AccessTokenModel at = checkAccessToken(accessToken);
        if (at.clientId.equals(clientId) == false) {
            throw new OAuth2Exception("无效client_id：" + clientId);
        }
        checkClientSecret(clientId, clientSecret);
        return at;
    }

    // ------------------- convert 数据转换

    /**
     * 将 Code 转换为 Access-Token
     *
     * @param cm CodeModel对象
     * @return AccessToken对象
     */
    default AccessTokenModel convertCodeToAccessToken(CodeModel cm) throws OAuth2Exception {
        AccessTokenModel at = new AccessTokenModel();
        at.accessToken = randomAccessToken(cm.clientId, cm.loginId, cm.scope);
        // at.refreshToken = randomRefreshToken(cm.clientId, cm.loginId, cm.scope);
        at.clientId = cm.clientId;
        at.loginId = cm.loginId;
        at.scope = cm.scope;
        at.openid = getOpenid(cm.clientId, cm.loginId);
        at.expiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getAccessTokenTimeout() * 1000);
        // at.refreshExpiresTime = System.currentTimeMillis() + (checkClientModel(cm.clientId).getRefreshTokenTimeout() * 1000);
        return at;
    }

    /**
     * 将 Access-Token 转换为 Refresh-Token
     *
     * @param at .
     * @return .
     */
    default RefreshTokenModel convertAccessTokenToRefreshToken(AccessTokenModel at) throws OAuth2Exception {
        RefreshTokenModel rt = new RefreshTokenModel();
        rt.refreshToken = randomRefreshToken(at.clientId, at.loginId, at.scope);
        rt.clientId = at.clientId;
        rt.loginId = at.loginId;
        rt.scope = at.scope;
        rt.openid = at.openid;
        rt.expiresTime = System.currentTimeMillis() + (checkClientModel(at.clientId).getRefreshTokenTimeout() * 1000);
        // 改变at属性
        at.refreshToken = rt.refreshToken;
        at.refreshExpiresTime = rt.expiresTime;
        return rt;
    }

    /**
     * 将 Refresh-Token 转换为 Access-Token
     *
     * @param rt .
     * @return .
     */
    default AccessTokenModel convertRefreshTokenToAccessToken(RefreshTokenModel rt) throws OAuth2Exception {
        AccessTokenModel at = new AccessTokenModel();
        at.accessToken = randomAccessToken(rt.clientId, rt.loginId, rt.scope);
        at.refreshToken = rt.refreshToken;
        at.clientId = rt.clientId;
        at.loginId = rt.loginId;
        at.scope = rt.scope;
        at.openid = rt.openid;
        at.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getAccessTokenTimeout() * 1000);
        at.refreshExpiresTime = rt.expiresTime;
        return at;
    }

    /**
     * 根据 Refresh-Token 创建一个新的 Refresh-Token
     *
     * @param rt .
     * @return .
     */
    default RefreshTokenModel convertRefreshTokenToRefreshToken(RefreshTokenModel rt) throws OAuth2Exception {
        RefreshTokenModel newRt = new RefreshTokenModel();
        newRt.refreshToken = randomRefreshToken(rt.clientId, rt.loginId, rt.scope);
        newRt.expiresTime = System.currentTimeMillis() + (checkClientModel(rt.clientId).getRefreshTokenTimeout() * 1000);
        newRt.clientId = rt.clientId;
        newRt.scope = rt.scope;
        newRt.loginId = rt.loginId;
        newRt.openid = rt.openid;
        return newRt;
    }

    // ------------------- save 数据

    /**
     * 持久化：Code-Model
     *
     * @param c .
     */
    default void saveCode(CodeModel c) {
        if (c == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().setObject(splicingCodeSaveKey(c.code), c, getoAuth2Engine().getoAuth2Config().getCodeTimeout());
    }

    /**
     * 持久化：Code-索引
     *
     * @param c .
     */
    default void saveCodeIndex(CodeModel c) {
        if (c == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, getoAuth2Engine().getoAuth2Config().getCodeTimeout());
    }

    /**
     * 持久化：AccessToken-Model
     *
     * @param at .
     */
    default void saveAccessToken(AccessTokenModel at) {
        if (at == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().setObject(splicingAccessTokenSaveKey(at.accessToken), at, at.getExpiresIn());
    }

    /**
     * 持久化：AccessToken-索引
     *
     * @param at .
     */
    default void saveAccessTokenIndex(AccessTokenModel at) {
        if (at == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingAccessTokenIndexKey(at.clientId, at.loginId), at.accessToken, at.getExpiresIn());
    }

    /**
     * 持久化：RefreshToken-Model
     *
     * @param rt .
     */
    default void saveRefreshToken(RefreshTokenModel rt) {
        if (rt == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().setObject(splicingRefreshTokenSaveKey(rt.refreshToken), rt, rt.getExpiresIn());
    }

    /**
     * 持久化：RefreshToken-索引
     *
     * @param rt .
     */
    default void saveRefreshTokenIndex(RefreshTokenModel rt) {
        if (rt == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingRefreshTokenIndexKey(rt.clientId, rt.loginId), rt.refreshToken, rt.getExpiresIn());
    }

    /**
     * 持久化：ClientToken-Model
     *
     * @param ct .
     */
    default void saveClientToken(ClientTokenModel ct) {
        if (ct == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().setObject(splicingClientTokenSaveKey(ct.clientToken), ct, ct.getExpiresIn());
    }

    /**
     * 持久化：ClientToken-索引
     *
     * @param ct .
     */
    default void saveClientTokenIndex(ClientTokenModel ct) {
        if (ct == null) {
            return;
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingClientTokenIndexKey(ct.clientId), ct.clientToken, ct.getExpiresIn());
    }

    /**
     * 持久化：Past-Token-索引
     *
     * @param ct .
     */
    default void savePastTokenIndex(ClientTokenModel ct) throws OAuth2Exception {
        if (ct == null) {
            return;
        }
        Long ttl = ct.getExpiresIn();
        ClientModel cm = checkClientModel(ct.clientId);
        if (cm.getPastClientTokenTimeout() != -1) {
            ttl = cm.getPastClientTokenTimeout();
        }
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingPastTokenIndexKey(ct.clientId), ct.clientToken, ttl);
    }

    /**
     * 持久化：用户授权记录
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @param scope    权限列表(多个逗号隔开)
     */
    default void saveGrantScope(String clientId, Object loginId, String scope) throws OAuth2Exception {
        if (FoxUtil.isEmpty(scope) == false) {
            long ttl = checkClientModel(clientId).getAccessTokenTimeout();
            getoAuth2Engine().getAuthEngine().getAuthTokenDao().set(splicingGrantScopeKey(clientId, loginId), scope, ttl);
        }
    }

    // ------------------- get 数据

    /**
     * 获取：Code Model
     *
     * @param code .
     * @return .
     */
    default CodeModel getCode(String code) {
        if (code == null) {
            return null;
        }
        return (CodeModel) getoAuth2Engine().getAuthEngine().getAuthTokenDao().getObject(splicingCodeSaveKey(code));
    }

    /**
     * 获取：Code Value
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return .
     */
    default String getCodeValue(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingCodeIndexKey(clientId, loginId));
    }

    /**
     * 获取：Access-Token Model
     *
     * @param accessToken .
     * @return .
     */
    default AccessTokenModel getAccessToken(String accessToken) {
        if (accessToken == null) {
            return null;
        }
        return (AccessTokenModel) getoAuth2Engine().getAuthEngine().getAuthTokenDao().getObject(splicingAccessTokenSaveKey(accessToken));
    }

    /**
     * 获取：Access-Token Value
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return .
     */
    default String getAccessTokenValue(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingAccessTokenIndexKey(clientId, loginId));
    }

    /**
     * 获取：Refresh-Token Model
     *
     * @param refreshToken .
     * @return .
     */
    default RefreshTokenModel getRefreshToken(String refreshToken) {
        if (refreshToken == null) {
            return null;
        }
        return (RefreshTokenModel) getoAuth2Engine().getAuthEngine().getAuthTokenDao().getObject(splicingRefreshTokenSaveKey(refreshToken));
    }

    /**
     * 获取：Refresh-Token Value
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return .
     */
    default String getRefreshTokenValue(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingRefreshTokenIndexKey(clientId, loginId));
    }

    /**
     * 获取：Client-Token Model
     *
     * @param clientToken .
     * @return .
     */
    default ClientTokenModel getClientToken(String clientToken) {
        if (clientToken == null) {
            return null;
        }
        return (ClientTokenModel) getoAuth2Engine().getAuthEngine().getAuthTokenDao().getObject(splicingClientTokenSaveKey(clientToken));
    }

    /**
     * 获取：Client-Token Value
     *
     * @param clientId 应用id
     * @return .
     */
    default String getClientTokenValue(String clientId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingClientTokenIndexKey(clientId));
    }

    /**
     * 获取：Past-Token Value
     *
     * @param clientId 应用id
     * @return .
     */
    default String getPastTokenValue(String clientId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingPastTokenIndexKey(clientId));
    }

    /**
     * 获取：用户授权记录
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return 权限
     */
    default String getGrantScope(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getAuthTokenDao().get(splicingGrantScopeKey(clientId, loginId));
    }

    // ------------------- delete数据

    /**
     * 删除：Code
     *
     * @param code 值
     */
    default void deleteCode(String code) {
        if (code != null) {
            getoAuth2Engine().getAuthEngine().getAuthTokenDao().deleteObject(splicingCodeSaveKey(code));
        }
    }

    /**
     * 删除：Code索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     */
    default void deleteCodeIndex(String clientId, Object loginId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingCodeIndexKey(clientId, loginId));
    }

    /**
     * 删除：Access-Token
     *
     * @param accessToken 值
     */
    default void deleteAccessToken(String accessToken) {
        if (accessToken != null) {
            getoAuth2Engine().getAuthEngine().getAuthTokenDao().deleteObject(splicingAccessTokenSaveKey(accessToken));
        }
    }

    /**
     * 删除：Access-Token索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     */
    default void deleteAccessTokenIndex(String clientId, Object loginId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingAccessTokenIndexKey(clientId, loginId));
    }

    /**
     * 删除：Refresh-Token
     *
     * @param refreshToken 值
     */
    default void deleteRefreshToken(String refreshToken) {
        if (refreshToken != null) {
            getoAuth2Engine().getAuthEngine().getAuthTokenDao().deleteObject(splicingRefreshTokenSaveKey(refreshToken));
        }
    }

    /**
     * 删除：Refresh-Token索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     */
    default void deleteRefreshTokenIndex(String clientId, Object loginId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingRefreshTokenIndexKey(clientId, loginId));
    }

    /**
     * 删除：Client-Token
     *
     * @param clientToken 值
     */
    default void deleteClientToken(String clientToken) {
        if (clientToken != null) {
            getoAuth2Engine().getAuthEngine().getAuthTokenDao().deleteObject(splicingClientTokenSaveKey(clientToken));
        }
    }

    /**
     * 删除：Client-Token索引
     *
     * @param clientId 应用id
     */
    default void deleteClientTokenIndex(String clientId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingClientTokenIndexKey(clientId));
    }

    /**
     * 删除：Past-Token索引
     *
     * @param clientId 应用id
     */
    default void deletePastTokenIndex(String clientId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingPastTokenIndexKey(clientId));
    }

    /**
     * 删除：用户授权记录
     *
     * @param clientId 应用id
     * @param loginId  账号id
     */
    default void deleteGrantScope(String clientId, Object loginId) {
        getoAuth2Engine().getAuthEngine().getAuthTokenDao().delete(splicingGrantScopeKey(clientId, loginId));
    }

    // ------------------- Random数据

    /**
     * 随机一个 Code
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @param scope    权限
     * @return Code
     */
    default String randomCode(String clientId, Object loginId, String scope) {
        return FoxUtil.getRandomString(60);
    }

    /**
     * 随机一个 Access-Token
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @param scope    权限
     * @return Access-Token
     */
    default String randomAccessToken(String clientId, Object loginId, String scope) {
        return FoxUtil.getRandomString(60);
    }

    /**
     * 随机一个 Refresh-Token
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @param scope    权限
     * @return Refresh-Token
     */
    default String randomRefreshToken(String clientId, Object loginId, String scope) {
        return FoxUtil.getRandomString(60);
    }

    /**
     * 随机一个 Client-Token
     *
     * @param clientId 应用id
     * @param scope    权限
     * @return Client-Token
     */
    default String randomClientToken(String clientId, String scope) {
        return FoxUtil.getRandomString(60);
    }

    // ------------------- 拼接key

    /**
     * 拼接key：Code持久化
     *
     * @param code 授权码
     * @return key
     */
    default String splicingCodeSaveKey(String code) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:code:" + code;
    }

    /**
     * 拼接key：Code索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return key
     */
    default String splicingCodeIndexKey(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:code-index:" + clientId + ":" + loginId;
    }

    /**
     * 拼接key：Access-Token持久化
     *
     * @param accessToken accessToken
     * @return key
     */
    default String splicingAccessTokenSaveKey(String accessToken) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:access-token:" + accessToken;
    }

    /**
     * 拼接key：Access-Token索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return key
     */
    default String splicingAccessTokenIndexKey(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:access-token-index:" + clientId + ":" + loginId;
    }

    /**
     * 拼接key：Refresh-Token持久化
     *
     * @param refreshToken refreshToken
     * @return key
     */
    default String splicingRefreshTokenSaveKey(String refreshToken) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken;
    }

    /**
     * 拼接key：Refresh-Token索引
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return key
     */
    default String splicingRefreshTokenIndexKey(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:refresh-token-index:" + clientId + ":" + loginId;
    }

    /**
     * 拼接key：Client-Token持久化
     *
     * @param clientToken clientToken
     * @return key
     */
    default String splicingClientTokenSaveKey(String clientToken) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:client-token:" + clientToken;
    }

    /**
     * 拼接key：Past-Token 索引
     *
     * @param clientId clientId
     * @return key
     */
    default String splicingClientTokenIndexKey(String clientId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:client-token-indedx:" + clientId;
    }

    /**
     * 拼接key：Past-Token 索引
     *
     * @param clientId clientId
     * @return key
     */
    default String splicingPastTokenIndexKey(String clientId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:past-token-indedx:" + clientId;
    }

    /**
     * 拼接key：用户授权记录
     *
     * @param clientId 应用id
     * @param loginId  账号id
     * @return key
     */
    default String splicingGrantScopeKey(String clientId, Object loginId) {
        return getoAuth2Engine().getAuthEngine().getConfig().getTokenName() + ":oauth2:grant-scope:" + clientId + ":" + loginId;
    }

}
